CodeMirror.defineMode("markdown", function (cmCfg, modeCfg) {

  var htmlMode = CodeMirror.getMode(cmCfg, { name:'xml', htmlMode:true });

  var header = 'header'
    , code = 'code'
    , quote = 'quote'
    , list = 'list'
    , hr = 'hr'
    , linktext = 'linktext'
    , linkhref = 'linkhref'
    , em = 'em'
    , strong = 'strong'
    , emstrong = 'emstrong';

  var hrRE = /^[*-=_]/
    , ulRE = /^[*-+]\s+/
    , olRE = /^[0-9]\.\s+/
    , headerRE = /^(?:\={3,}|-{3,})$/
    , codeRE = /^(k:\t|\s{4,})/
    , textRE = /^[^\[*_\\<>`]+/;

  function switchInline(stream, state, f) {
    state.f = state.inline = f;
    return f(stream, state);
  }

  function switchBlock(stream, state, f) {
    state.f = state.block = f;
    return f(stream, state);
  }


  // Blocks

  function blockNormal(stream, state) {
    if (stream.match(codeRE)) {
      stream.skipToEnd();
      return code;
    }

    if (stream.eatSpace()) {
      return null;
    }

    if (stream.peek() === '#' || stream.match(headerRE)) {
      stream.skipToEnd();
      return header;
    }
    if (stream.eat('>')) {
      state.indentation++;
      return quote;
    }
    if (stream.peek() === '<') {
      return switchBlock(stream, state, htmlBlock);
    }
    if (stream.peek() === '[') {
      return switchInline(stream, state, footnoteLink);
    }
    if (hrRE.test(stream.peek())) {
      var re = new RegExp('(?:\s*[' + stream.peek() + ']){3,}$');
      if (stream.match(re, true)) {
        return hr;
      }
    }

    var match;
    if (match = stream.match(ulRE, true) || stream.match(olRE, true)) {
      state.indentation += match[0].length;
      return list;
    }

    return switchInline(stream, state, state.inline);
  }

  function htmlBlock(stream, state) {
    var type = htmlMode.token(stream, state.htmlState);
    if (stream.eol() && !state.htmlState.context) {
      state.block = blockNormal;
    }
    return type;
  }


  // Inline

  function inlineNormal(stream, state) {
    function getType() {
      return state.strong ? (state.em ? emstrong : strong)
        : (state.em ? em : null);
    }

    if (stream.match(textRE, true)) {
      return getType();
    }

    var ch = stream.next();

    if (ch === '\\') {
      stream.next();
      return getType();
    }
    if (ch === '`') {
      return switchInline(stream, state, inlineElement(code, '`'));
    }
    if (ch === '<') {
      return switchInline(stream, state, inlineElement(linktext, '>'));
    }
    if (ch === '[') {
      return switchInline(stream, state, linkText);
    }

    var t = getType();
    if (ch === '*' || ch === '_') {
      if (stream.eat(ch)) {
        return (state.strong = !state.strong) ? getType() : t;
      }
      return (state.em = !state.em) ? getType() : t;
    }

    return getType();
  }

  function linkText(stream, state) {
    while (!stream.eol()) {
      var ch = stream.next();
      if (ch === '\\') stream.next();
      if (ch === ']') {
        state.inline = state.f = linkHref;
        return linktext;
      }
    }
    return linktext;
  }

  function linkHref(stream, state) {
    stream.eatSpace();
    var ch = stream.next();
    if (ch === '(' || ch === '[') {
      return switchInline(stream, state, inlineElement(linkhref, ch === '(' ? ')' : ']'));
    }
    return 'error';
  }

  function footnoteLink(stream, state) {
    if (stream.match(/^[^\]]*\]:/, true)) {
      state.f = footnoteUrl;
      return linktext;
    }
    return switchInline(stream, state, inlineNormal);
  }

  function footnoteUrl(stream, state) {
    stream.eatSpace();
    stream.match(/^[^\s]+/, true);
    state.f = state.inline = inlineNormal;
    return linkhref;
  }

  function inlineElement(type, endChar, next) {
    next = next || inlineNormal;
    return function (stream, state) {
      while (!stream.eol()) {
        var ch = stream.next();
        if (ch === '\\') stream.next();
        if (ch === endChar) {
          state.inline = state.f = next;
          return type;
        }
      }
      return type;
    };
  }

  return {
    startState:function () {
      return {
        f:blockNormal,

        block:blockNormal,
        htmlState:htmlMode.startState(),
        indentation:0,

        inline:inlineNormal,
        em:false,
        strong:false
      };
    },

    copyState:function (s) {
      return {
        f:s.f,

        block:s.block,
        htmlState:CodeMirror.copyState(htmlMode, s.htmlState),
        indentation:s.indentation,

        inline:s.inline,
        em:s.em,
        strong:s.strong
      };
    },

    token:function (stream, state) {
      if (stream.sol()) {
        state.f = state.block;
        var previousIndentation = state.indentation
          , currentIndentation = 0;
        while (previousIndentation > 0) {
          if (stream.eat(' ')) {
            previousIndentation--;
            currentIndentation++;
          } else if (previousIndentation >= 4 && stream.eat('\t')) {
            previousIndentation -= 4;
            currentIndentation += 4;
          } else {
            break;
          }
        }
        state.indentation = currentIndentation;

        if (currentIndentation > 0) return null;
      }
      return state.f(stream, state);
    }
  };

});

CodeMirror.defineMIME("text/x-markdown", "markdown");
